/**
 * the rhythm pattern is used to control the background rhythm. in othere words, it is a 
 * rhythm player. The main task of this class is high precision time scheduling in javascript
 * our system implemented some fixed patterns like 
 * epic, china new age and dub step and etc. besides, there is a random pattern creator program to create random
 * music rhythm patterns. some effects, like swing, chaos added to pattern
 */
jweb.util.RhythmPlayer = function() {
  this.subscriberId=null;
  this.useWavetable=false;
  /*default tempo*/
  this.tempo = 100;
  this.secondsPerBeat = 60.0/100;
  this.noteDurationSec = 0;
  this.fastNoteTimes = [];//used to check note time by a simple query not complex multipling operation
  /*web worker reference for handling*/
  this.workerHandler = null;
  this.scheduleAheadTime = 0.08;    // How far ahead to schedule audio (sec)
  this.lookahead = 25.0;
  this.playing = false;
  this.loopPlay=false;
  this.audioContext=null;
  
  this.currentNoteIndex =0;
  this.currentNoteTime = 0;
  
  this.isMajor = true;//current rhythm type major or minor
  this.led = $('#arp .leds').leds();
  this.chord = $('#arp .chrod-name').chrod({});
  this.midiOffset = 0;
  
  this.noteLength = 1.0;// 100%
  
  this.currentPattern = null;
  this.synth1 = null;
  this.wml=null;
  this.notesOn=[false, false, false, false, false, false, false, false, false, false, false, false];
  this.start();
}
jweb.util.RhythmPlayer.prototype= {
	onMessage:function(subj, msg, data) {
		if(subj == constants.midiMessage){
			var data = msg.event.data;
			var cmd = data[0] >> 4;
			var channel = data[0] & 0xf;
			var type = data[0] & 0xf0; // channel agnostic message type.
			var note = data[1];
			var velocity = data[2];
			
			switch(type){
				case 144: // noteOn message
					if(velocity==0) this.notesOn[note%12] = false;//if velocity is zero, it is noteoff
					else this.notesOn[note%12] = true;
					break;
				case 128: // noteOff message 
					this.notesOn[note%12] = false;
					//this.midiOffset = 0;
					break;
				case 176://
					if (note == 18){//now note is cccc
						this.synth1.onUpdateFilterQ1(velocity);
					}
					if (note == 19){//now note is cccc
						this.synth1.onUpdateFilterQ2(velocity);
					}
					if (note == 20){//now note is cccc .onUpdateModOsc2(val);
						this.synth1.onUpdateModOsc2(velocity);
					}
					if (note == 21){//now note is cccc
						this.synth1.onUpdateModOsc2(velocity);
					}
					
					if (note == 22){//now note is cccc
						this.synth1.onUpdateVolume(velocity);
					}
					break;
			}
			
			//then detect chord
			var ns = this.notesOn;
			if(ns[0]){
				this.chord.setValue('Cmaj');this.midiOffset = 0;
				this.isMajor = true;
			}
			if(ns[0]&&ns[4]&&ns[7]){
				this.chord.setValue('Cmaj');this.midiOffset = 0;
				this.isMajor = true;
			}
			else if(ns[0]&&ns[11]){
				this.chord.setValue('Cmin');this.isMajor = false;this.midiOffset = 0;
			}
			else if(ns[0]&&ns[3]&&ns[7]){
				this.chord.setValue('Cmin');this.isMajor = false;this.midiOffset = 0;
			}
			else if(ns[1]){
				this.chord.setValue('#Cmaj');this.midiOffset = 1;
				this.isMajor = true;
			}
			else if(ns[1]&&ns[5]&&ns[6]){
				this.chord.setValue('#Cmaj');this.midiOffset = 1;
				this.isMajor = true;
			}
			
			else if(ns[0]&&ns[1]){
				this.chord.setValue('#Cmin');this.isMajor = false;this.midiOffset = 1;
			}
			else if(ns[1]&&ns[4]&&ns[6]){
				this.chord.setValue('#Cmin');this.isMajor = false;this.midiOffset = 1;
			}
			
			else if(ns[2]){
				this.chord.setValue('Dmaj');this.midiOffset = 2;
				this.isMajor = true;
			}
			else if(ns[2]&&ns[6]&&ns[9]){
				this.chord.setValue('Dmaj');this.midiOffset = 2;
				this.isMajor = true;
			}
			
			else if(ns[2]&&ns[1]){
				this.chord.setValue('Dmin');this.isMajor = false;this.midiOffset = 2;
			}
			else if(ns[2]&&ns[5]&&ns[9]){
				this.chord.setValue('Dmin');this.isMajor = false;this.midiOffset = 2;
			}
			//bEmaj	bE	bE+G+bB
			else if(ns[3]){
				this.chord.setValue('bEmaj');this.midiOffset = 3;
				this.isMajor = true;
			}
			else if(ns[3]&&ns[7]&&ns[10]){
				this.chord.setValue('bEmaj');this.midiOffset = 3;
				this.isMajor = true;
			}
			//bEmin	bE+D	bE+bG+bB

			else if(ns[3]&&ns[2]){
				this.chord.setValue('bEmin');this.isMajor = false;this.midiOffset = 3;
			}
			else if(ns[3]&&ns[6]&&ns[10]){
				this.chord.setValue('bEmin');this.isMajor = false;this.midiOffset = 3;
			}
			//Emaj	E	E+#G+B
			else if(ns[4]){
				this.chord.setValue('Emaj');this.midiOffset = 4;
				this.isMajor = true;
			}
			else if(ns[4]&&ns[8]&&ns[11]){
				this.chord.setValue('Emaj');this.midiOffset = 4;
				this.isMajor = true;
			}
			
			//Emin	E+bE	E+G+B
			else if(ns[4]&&ns[3]){
				this.chord.setValue('Emin');this.isMajor = false;this.midiOffset = 4;
			}
			else if(ns[4]&&ns[7]&&ns[11]){
				this.chord.setValue('Emin');this.isMajor = false;this.midiOffset = 4;
			}
			
			//Fmaj	F	F+A+C
			else if(ns[5]){
				this.chord.setValue('Fmaj');this.midiOffset = 5;
				this.isMajor = true;
			}
			else if(ns[5]&&ns[0]&&ns[9]){
				this.chord.setValue('Fmaj');this.midiOffset = 5;
				this.isMajor = true;
			}
			//Fmin	F+E	F+bA+C
			else if(ns[5]&&ns[4]){
				this.chord.setValue('Fmin');this.isMajor = false;this.midiOffset = 5;
			}
			else if(ns[5]&&ns[0]&&ns[8]){
				this.chord.setValue('Fmin');this.isMajor = false;this.midiOffset = 5;
			}
			//#Fmaj	#F	#F+#A+#C
			else if(ns[6]){
				this.chord.setValue('#Fmaj');this.midiOffset = 6;
				this.isMajor = true;
			}
			else if(ns[6]&&ns[10]&&ns[1]){
				this.chord.setValue('#Fmaj');this.midiOffset = 6;
				this.isMajor = true;
			}
			//#Fmin	#F+F	#F+A+#C
			else if(ns[6]&&ns[5]){
				this.chord.setValue('#Fmin');this.isMajor = false;this.midiOffset = 6;
			}
			else if(ns[6]&&ns[9]&&ns[1]){
				this.chord.setValue('#Fmin');this.isMajor = false;this.midiOffset = 6;
			}
			//Gmaj	G	G+B+D
			else if(ns[7]){
				this.chord.setValue('Gmaj');this.midiOffset = 7;
				this.isMajor = true;
			}
			else if(ns[7]&&ns[11]&&ns[2]){
				this.chord.setValue('Gmaj');this.midiOffset = 7;
				this.isMajor = true;
			}
			//Gmin	G+#F	G+bB+D
			else if(ns[7]&&ns[6]){
				this.chord.setValue('Gmin');this.isMajor = false;this.midiOffset = 7;
			}
			else if(ns[7]&&ns[9]&&ns[2]){
				this.chord.setValue('Gmin');this.isMajor = false;this.midiOffset = 7;
			}
			//bAmaj	bA	bA+C+bE
			else if(ns[8]){
				this.chord.setValue('bAmaj');this.midiOffset = 8;
				this.isMajor = true;
			}
			else if(ns[8]&&ns[0]&&ns[3]){
				this.chord.setValue('bAmaj');this.midiOffset = 8;
				this.isMajor = true;
			}
			//bAmin	bA+G	bA+B+bE
			else if(ns[7]&&ns[6]){
				this.chord.setValue('bAmin');this.isMajor = false;this.midiOffset = 8;
			}
			else if(ns[7]&&ns[11]&&ns[4]){
				this.chord.setValue('bAmin');this.isMajor = false;this.midiOffset = 8;
			}
			//Amaj	A	A+#C+E
			else if(ns[9]){
				this.chord.setValue('Amaj');this.midiOffset = 9;
				this.isMajor = true;
			}
			else if(ns[9]&&ns[1]&&ns[4]){
				this.chord.setValue('Amaj');this.midiOffset = 9;
				this.isMajor = true;
			}
			//Amin	A+bA	A+C+E
			else if(ns[9]&&ns[8]){
				this.chord.setValue('Amin');this.isMajor = false;this.midiOffset = 9;
			}
			else if(ns[9]&&ns[0]&&ns[4]){
				this.chord.setValue('Amin');this.isMajor = false;this.midiOffset = 9;
			}
			//bBmaj	bB	bB+D+F
			else if(ns[10]){
				this.chord.setValue('bBmaj');this.midiOffset = 10;
				this.isMajor = true;
			}
			else if(ns[10]&&ns[2]&&ns[5]){
				this.chord.setValue('bBmaj');this.midiOffset = 10;
				this.isMajor = true;
			}
			//bBmin	bB+A	bB+bD+F
			else if(ns[10]&&ns[9]){
				this.chord.setValue('bBmin');this.isMajor = false;this.midiOffset = 10;
			}
			else if(ns[10]&&ns[1]&&ns[5]){
				this.chord.setValue('bBmin');this.midiOffset = 10;
				this.isMajor = false;
			}
			//Bmaj	B	B+#D+#F
			else if(ns[11]){
				this.chord.setValue('Bmaj');this.midiOffset = 11;
				this.isMajor = true;
			}
			else if(ns[11]&&ns[3]&&ns[6]){
				this.chord.setValue('Bmaj');this.midiOffset = 11;
				this.isMajor = true;
			}
			//Bmin	B+bB	B+D+#F
			else if(ns[11]&&ns[10]){
				this.chord.setValue('Bmin');this.midiOffset = 11;
				this.isMajor = false;
			}
			else if(ns[10]&&ns[2]&&ns[6]){
				this.chord.setValue('Bmin');this.midiOffset = 11;
				this.isMajor = false;
			}
		}
		else if(subj == constants.contextInited){
			this.audioContext = msg.audioContext;
			
			//begin to bind
			this.currentNoteTime = this.audioContext.currentTime;//set currentNoteTime though is not used now
			this.workerHandler = new Worker("resources/js/musicworker.js");
			var $this = this;
			this.workerHandler.onmessage = function(e) {
		        if (e.data == "schedule") {
		        	$this._coreSchedule();//message back from worker
		        }
		        else{
		        	console.log("message: " + e.data);
		        }
		    };
		    this.workerHandler.postMessage({command:'interval',interval:this.lookahead});
		}
		else if(subj == constants.synth1Ready){
			this.synth1=msg.synth;
		}
		else if(subj == constants.synth2Ready){
			var wml = this.wml=msg.synth;
			
		}
	},
	start:function() {
		if(this.subscriberId==null) this.subscriberId='**';
		this.subscriberId = window.PageBus.subscribe('**', this, this.onMessage, null);
	},
	stop:function() {
		if(this.subscriberId!=null){
			window.PageBus.unsubscribe(this.subscriberId);
			this.subscriberId=null;
		}
	},
	setTempo:function(tp){
		this.temp = tp;
		var secPerBeat=60.0/tp;
		this.secondsPerBeat = secPerBeat;
		//update fast note time table
		this.fastNoteTimes = [secPerBeat*4/*whole note*/, secPerBeat*2/*half note*/, secPerBeat/*quarter note*/, secPerBeat/2/*8th note*/, secPerBeat/4/*16th note*/, secPerBeat/8/*32th*/];
	},
	/*
	 * ptn is the jweb.util.MusicPattern object
	 * pl is boolean value telling loop the pattern or not  
	 */
	play:function(ptn, lp){
		if(this.wml)this.wml.synth.programChange(0, 35);//change to "Slap Bass 1",
		
		this.currentPattern = ptn;
		this.loopPlay = lp;
		this.playing =!this.playing;
		
		if (this.playing) { // start playing
			this.setTempo(this.tempo);//trigger a tempo change
			this.currentNoteIndex = 0;//reset note pointer
			this.currentNoteTime = this.audioContext.currentTime;//reset time, it is like start point, i.e. zero
			this.noteDurationSec = this.fastNoteTimes[ this.currentPattern.m[0].maj[2] ]; //first duration will be next noteon
	        this.workerHandler.postMessage({command:'start'});//post command
	        return 'Stop';
	    } else {
	    	this.workerHandler.postMessage({command:'stop'});
	    	return 'Start';
	    }
	},
	setPattern:function(p){
		this.currentPattern = p;
	},
	/*
	 * schedule the notes used overlap ahead approach
	 **/
	_coreSchedule:function() {
	    // while there are notes that will need to play before the next interval, 
	    // schedule them and advance the pointer.
	    while (this.currentNoteTime < this.audioContext.currentTime + this.scheduleAheadTime ) {
	        this.sendCurrentMidiMessageToDevice();
	        if(!this.nextMidiMessage()){
	        	this.workerHandler.postMessage({command:'stop'});//break the loop and send cmd to stop timer in worker
	        	break;
	        }
	    }
	},
	sendCurrentMidiMessageToDevice:function(){
		var ledPin = [0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0];
		ledPin[this.currentNoteIndex*2] = 2;
		ledPin[this.currentNoteIndex*2+1] = 1;
		this.led.setValue(ledPin);
		
		var main = (this.isMajor?this.currentPattern.m[this.currentNoteIndex].maj:this.currentPattern.m[this.currentNoteIndex].min);
		var bass = this.currentPattern.m[this.currentNoteIndex].bass;
		
		this.synth1.scheduleNote(main[0]+this.midiOffset, main[1], this.currentNoteTime, this.currentNoteTime+this.noteDurationSec*this.noteLength);
		//this.wml.synth.scheduleNote(0, bass[0], bass[1], this.currentNoteTime, this.currentNoteTime+this.noteDurationSec);
	},
	/*return true if has next*/
	nextMidiMessage:function(){
		if(this.currentNoteIndex>=this.currentPattern.m.length-1){//the music pattern played one time and point at end
			if(this.loopPlay){
				this.currentNoteIndex = 0;//if loop play go back to first note
				this.currentNoteTime += this.noteDurationSec;//push ahead the last note duration
				this.noteDurationSec = this.fastNoteTimes[ this.currentPattern.m[0].maj[2]]; //first duration will be next note on
				return true;
			}
			else {
				return false;
			}
		}
		else{
			this.currentNoteIndex ++;
			this.currentNoteTime += this.noteDurationSec;//push ahead the last note duration
			this.noteDurationSec = this.fastNoteTimes[ this.currentPattern.m[this.currentNoteIndex].maj[2] ]; //first duration will be next note on
			/*
			if(this.currentPattern.midiMessages[this.currentNoteIndex].multi){
				this.noteDurationSec *= this.currentPattern.midiMessages[this.currentNoteIndex].multi;
			}
			*/
			return true;
		}
	}
}